-- Testing framework

DEBUGGER_ON = 1 -- pause() will trip the debugger

require 'debugging'

test_modules = {
    "networking",
    "threads",
    "messages",
    "logging"
}

-- if we have args, replace the set of all things to be tested with the parameters
-- this means you can tell tests.lua to test specific modules
if (arg[1]) then
    test_modules = arg
end

function performAllTests()

    local t -- test result info
    local failedModules = {}
    
    for _,testmodule in ipairs(test_modules) do
        print("----------")
        print("Testing module " .. testmodule .. "...\n")
        
        -- Wrap this in a pcall to trap things like syntax errors
        require ('tests.'..testmodule)
        
        t = testModule(tests[testmodule])
        print("\nResults for " .. testmodule .. ":")
        print("\t" .. t.pass .. " of " .. t.total .. " passed (" .. t.passPercent .. "%)")
--        print("\t" .. t.fail .. " of " .. t.total .. " failed")
        if (t.skipped) then
            print("\t("..t.skipped.." skipped)")
        end

        if (t.failedTests) then
            print ("\tFailed tests:")
            for _,mod in ipairs(t.failedTests) do
                print ("\t\t" .. mod)
            end
        end
            
        print("\tFinal result: ")
        if (t.passed == true) then
            print("\t\tPassed")
        else
            print("\t\tFAILED")
            table.insert(failedModules, testmodule)
        end
    end
    
    -- print a final report if we have more than one module to test
    if (# test_modules ~= 1) then
        print("\n==========")
        print("Final report:")
    
        if (# failedModules == 0) then
            print("\tAll modules passed.")
        else
            print("\tThe following modules failed:")
            for _,failure in ipairs(failedModules) do
                print("\t\t* " .. failure)
            end
        end
    end
    
end

function testModule(moduleToTest)
    local test, name = getUnfinishedTestInModule(moduleToTest)
    local passCount, failCount = 0,0
    local failingTests = {}
    
    -- Loop until all tests are done. We loop because if a test has unfinished prereqs, 
    -- we skip over it and try again later, hopefully once the prereqs are done.
    while (test) do

        local completedWithoutError, result
        
        if testPrereqsAllPassed(test, moduleToTest) == true then
            completedWithoutError, result = pcall(test.test)
        else
            completedWithoutError, result = false, "Prerequisites did not pass"
        end
        
        io.write (name .. ": ")
        if completedWithoutError == true and result == true then
            print ("Passed")
            passCount = passCount + 1
            test.passed = true
        else
            -- ooer
            print ("FAILED")
            if (completedWithoutError == false) then
                -- we have an error message to give
                print("\t"..result)
            end
            failCount = failCount + 1
            table.insert(failingTests, name)
            test.passed = false
        end
        test.completed = true
        
        test, name = getUnfinishedTestInModule(moduleToTest)
    end
    
    local totalTests = 0
    for _,_ in pairs(moduleToTest) do
        totalTests = totalTests + 1
    end
    
    -- tests are done, generate a report table
    local results = {
        pass = passCount,
        fail = failCount,
        total = totalTests,
        passPercent = math.floor(passCount / totalTests * 100),
        failedTests = failingTests
    }
    
    if (failCount > 0) then
        results.passed = false
    else
        results.passed = true
    end
    
    if (passCount + failCount ~= totalTests) then
        -- we skipped some
        results.skipped = totalTests - (passCount + failCount)
    end
    
    if # results.failedTests == 0 then
        results.failedTests = nil
    end
    
    return results
    
end

-- Return an unfinished test in the given module, or nil if all tests are complete.
function getUnfinishedTestInModule(module)
    for name,test in pairs(module) do
        if test.completed == nil and 
            testHasUnfinishedPrereqs(test,module) == false and
            test.test ~= nil and
            test.skip ~= true then
            return test, name
        end
    end
    return nil
end

-- Return true if a test in a given module has un-tested prerequisite test cases
function testHasUnfinishedPrereqs(test, module)
    -- skip the obvious case
    if test.prerequisites == nil then
        return false
    end
    
    for _,prereq in pairs(test.prerequisites) do
        if (module[prereq] == nil) then
            print("A test is asking for prerequisite \"" .. prereq .. "\" which doesn't exist in this module.")
            print("Did you construct your unit tests properly?")
            error("Testing cannot continue.")
        end
        if module[prereq].completed == nil then
            return true
        end
    end
    
    return false
end

-- Iterate over all the test's prerequisites. If any failed, return false. Otherwise,
-- return true.
function testPrereqsAllPassed(test, module)
    -- no prereqs = all good
    if test.prerequisites == nil then
        return true
    end
    
    for _,prereq in pairs(test.prerequisites) do
        if module[prereq].passed == false then
            return false
        end
    end
    
    return true
end

performAllTests()
